﻿using System;
using System.Threading;


namespace FireBus.Core
{
	public class VoidAsyncResult : IAsyncResult
	{
		private readonly AsyncCallback _Callback;
		
		private readonly Object _AsyncState;

		private Int32 _CompletedState = State.Pending;

		private ManualResetEvent _AsyncWaitHandle;

		private Exception _Exception;

		private readonly IMessageAccessor _MessageAccessor;
		

		public Object AsyncState
		{
			get { return _AsyncState; }
		}

		public Boolean CompletedSynchronously
		{
			get
			{
				return
					Thread.VolatileRead(ref _CompletedState) == State.CompletedSynchronously;
			}
		}

		public WaitHandle AsyncWaitHandle
		{
			get
			{
				if (_AsyncWaitHandle == null)
				{
					var done = IsCompleted;
					var mre = new ManualResetEvent(done);
					if (Interlocked.CompareExchange(ref _AsyncWaitHandle, mre, null) != null)
					{
						mre.Close();
					}
					else
					{
						if (!done && IsCompleted)
						{
							_AsyncWaitHandle.Set();
						}
					}
				}
				return _AsyncWaitHandle;
			}
		}

		public Boolean IsCompleted
		{
			get
			{
				return Thread.VolatileRead(ref _CompletedState) != State.Pending;
			}
		}


		public VoidAsyncResult(AsyncCallback asyncCallback, Object state, IMessageAccessor messageAccessor)
		{
			_Callback = asyncCallback;
			_AsyncState = state;
			if (messageAccessor == null)
			{
				throw new ArgumentNullException("messageAccessor");
			}
			_MessageAccessor = messageAccessor;
		}


		public void SetAsCompleted(
			Exception exception, Boolean completedSynchronously)
		{
			_Exception = exception;

			var prevState = Interlocked.Exchange(
				ref _CompletedState,
				completedSynchronously ? State.CompletedSynchronously : State.CompletedAsynchronously);

			if (prevState != State.Pending)
			{
				
				throw new InvalidOperationException(
					_MessageAccessor.AsyncResultStateCanBeSetOnlyOnce());
			}
			
			if (_AsyncWaitHandle != null)
			{
				_AsyncWaitHandle.Set();
			}

			if (_Callback != null)
			{
				_Callback(this);
			}
		}

		public void EndInvoke()
		{
			if (!IsCompleted)
			{
				AsyncWaitHandle.WaitOne();
				AsyncWaitHandle.Close();
				_AsyncWaitHandle = null;
			}

			if (_Exception != null)
			{
				throw _Exception;
			}
		}


		private static class State
		{
			public const Int32 Pending = 0;
			public const Int32 CompletedSynchronously = 1;
			public const Int32 CompletedAsynchronously = 2;
		}
	}
}
